package com.lovesher.payment.service.plugin.wechat;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;

import com.lovesher.payment.common.enums.NotifyMethod;
import com.lovesher.payment.common.enums.PayStatus;
import com.lovesher.payment.common.enums.PayType;
import com.lovesher.payment.common.resposity.bean.AccountRechargeDTO;
import com.lovesher.payment.common.resposity.bean.PaymentSerial;
import com.lovesher.payment.common.resposity.bean.PluginConfig;
import com.lovesher.payment.common.resposity.bean.PluginConfigAttribute;
import com.lovesher.payment.service.resposity.bean.PaymentInterface;
import com.lovesher.payment.service.resposity.dao.PaymentDao;
import com.lovesher.payment.service.resposity.mapper.PluginConfigAttributeMapper;

/**
 * @Title: WeixinCodePayPlugin.java
 * 
 * @Description: 微信扫码支付（PC）插件
 * 
 * @author 
 * 
 * @date 2015-2-26 上午10:03:43
 * 
 * @version V1.0
 */
@Component("weixinCodePayPlugin")
public class WeixinCodePayPlugin extends PaymentInterface {

	private static Log log = LogFactory.getLog(WeixinCodePayPlugin.class);
	
	@Resource
	private PluginConfigAttributeMapper pluginConfigAttributeMapper;
	@Resource
	private PaymentDao paymentDao;
	@Override
	public String getName() {
		return "微信扫码支付（PC）";
	}

	@Override
	public String getVersion() {
		return "1.0";
	}

	@Override
	public String getAuthor() {
		return "";
	}

	@Override
	public String getSiteUrl() {
		return "https://pay.weixin.qq.com";
	}

	@Override
	public String getRequestUrl() {
		return "https://api.mch.weixin.qq.com/pay/unifiedorder";
	}

	@Override
	public RequestMethod getRequestMethod() {
		return RequestMethod.post;
	}

	@Override
	public String getRequestCharset() {
		return "UTF-8";
	}

	@Override
	public Map<String,Object> verifyNotify(String sn, NotifyMethod notifyMethod, HttpServletRequest request,PaymentSerial paymentSerial) {
		Map<String,Object> resultMap = new HashMap<String,Object>();
		String result = "";
		InputStream inStream = null;
		try {
			inStream = request.getInputStream();
			ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int len = 0;
			while ((len = inStream.read(buffer)) != -1) {
				outSteam.write(buffer, 0, len);
			}
			outSteam.close();
			inStream.close();
			result = new String(outSteam.toByteArray(), "utf-8");
		} catch (IOException e) {
			log.error("",e);
			resultMap.put("success", Boolean.FALSE);
			return resultMap;
		}
		log.info(getName() + "通知参数验证,通知报文为:" + result);
		Map<String, String> map = null;

		if (StringUtils.isNotEmpty(result)) {
			request.setAttribute("resString", result);
			resultMap.put("returnText", result);
			try {
				map = XmlUtils.getMapFromXML(result);
			} catch (Exception e) {
				log.error(getName() + "通知参数验签，通知参数:" + map + ",转换为map异常，异常信息:" , e);
				resultMap.put("success", Boolean.FALSE);
				return resultMap;
			}
			log.info(getName() + "通知参数验签，通知参数:" + result);
			String return_code = map.get("return_code").toString();
			String result_code = map.get("result_code").toString();
			if (!"SUCCESS".equals(return_code) && !"SUCCESS".equals(result_code)) {
				log.info(getName() + "通知验签不通过，通知参数:验证签名不通过:" + map);
				resultMap.put("success", Boolean.FALSE);
			} else {
				resultMap.put("success", Boolean.TRUE);
			}
		}else {
			resultMap.put("success", Boolean.FALSE);
		}
		return resultMap;
	}

	@Override
	public String getNotifyMessage(String sn, NotifyMethod notifyMethod, HttpServletRequest request) {
		if (notifyMethod == NotifyMethod.async) {
			return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
		}
		return null;
	}

	@Override
	public Integer getTimeout() {
		return 21600;
	}

	/**
	 * 构造签名
	 * 
	 * @param params
	 * @param encode
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public static String createSign(Map<String, String> params, boolean encode) {
		Set<String> keysSet = params.keySet();
		Object[] keys = keysSet.toArray();
		Arrays.sort(keys);
		StringBuffer temp = new StringBuffer();
		boolean first = true;
		for (Object key : keys) {
			if (first) {
				first = false;
			} else {
				temp.append("&");
			}
			temp.append(key).append("=");
			Object value = params.get(key);
			String valueString = "";
			if (null != value) {
				valueString = value.toString();
			}
			if (encode) {
				try {
					temp.append(URLEncoder.encode(valueString, "UTF-8"));
				} catch (UnsupportedEncodingException e) {
					e.printStackTrace();
				}
			} else {
				temp.append(valueString);
			}
		}
		return temp.toString();
	}

	@Override
	public Map<String, String> getParameterMap(AccountRechargeDTO accountRechargeDTO, String serialSn) {
		PluginConfig pluginConfig = getPluginConfig();
		Map<String, Object> pluginConfigMap = new HashMap<String, Object>();
		List<PluginConfigAttribute> pluginConfigAttributes = pluginConfigAttributeMapper.findByPluginId(pluginConfig.getId());
		for (int i = 0; i < pluginConfigAttributes.size(); i++) {
			PluginConfigAttribute pluginConfigAttribute = pluginConfigAttributes.get(i);
			pluginConfigMap.put(pluginConfigAttribute.getName(), pluginConfigAttribute.getAttributes());
		}

		SortedMap<String, String> parameterMap = new TreeMap<String, String>();
		BigDecimal amount = new BigDecimal(accountRechargeDTO.getMoney().multiply(new BigDecimal(100)).doubleValue());
		String amountStr = amount.toString();
		parameterMap.put("appid", pluginConfigMap.get("appid").toString());// 微信分配的公众账号ID
		parameterMap.put("body", "盈米科技"); // TODO 商品或支付单简要描述。
		parameterMap.put("device_info", "WEB");// 终端设备号(门店号或收银设备ID)，注意：PC网页或公众号内支付请传"WEB"
		parameterMap.put("mch_id", pluginConfigMap.get("mch_id").toString());// 微信支付分配的商户号
		parameterMap.put("nonce_str", RandomStringUtils.random(32, "abcdefghijklmnopqrstuvwxyz0123456789"));// 随机字符串，不长于32位。

		parameterMap.put("out_trade_no", serialSn); // 商户系统内部的订单号,32个字符内、可包含字母
		parameterMap.put("total_fee", amountStr); // 订单总金额，只能为整数 (单位：分)
		parameterMap.put("spbill_create_ip",accountRechargeDTO.getAddip()); // APP和网页支付提交用户端ip，Native支付填调用微信支付API的机器IP。
		parameterMap.put("notify_url", super.getNotifyUrl(serialSn, accountRechargeDTO.getUserId(), NotifyMethod.async,PayType.valueOf(accountRechargeDTO.getType())));// 接收微信支付异步通知回调地址
		parameterMap.put("trade_type", "NATIVE");// 取值如下：JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付
//		parameterMap.put("openid", accountRechargeDTO.getOpenId());// trade_type=JSAPI，此参数必传，用户在商户appid下的唯一标识。

		String string1 = createSign(parameterMap, false);
		String stringSignTemp = string1 + "&key=" + pluginConfigMap.get("key").toString();
		log.info("待加密字符串:" + stringSignTemp);
		String signValue = DigestUtils.md5Hex(stringSignTemp).toUpperCase();
		parameterMap.put("sign", signValue);

		String result = WeixinPayUtils.sendPost(getRequestUrl(), parameterMap);

		if (StringUtils.isEmpty(result)) {
			return null;
		}

		Map<String, String> resultMap = new HashMap<String, String>();
		try {
			resultMap = XmlUtils.getMapFromXML(result);
		} catch (Exception e) {
			log.error("",e);
		}
		long timeStamp = System.currentTimeMillis() / 1000;
		resultMap.put("timeStamp", timeStamp+"");

		parameterMap.clear();
		parameterMap.put("appId",pluginConfigMap.get("appid").toString() );
		parameterMap.put("timeStamp", timeStamp + "");
		String nonceStr = RandomStringUtils.random(32, "abcdefghijklmnopqrstuvwxyz0123456789");
		parameterMap.put("nonceStr", nonceStr);
		parameterMap.put("signType", "MD5");
		parameterMap.put("package", "prepay_id=" + resultMap.get("prepay_id"));
		resultMap.put("nonce_str", nonceStr);
		String strCreate = createSign(parameterMap, false) + "&key=" + pluginConfigMap.get("key").toString();
		resultMap.put("sign", DigestUtils.md5Hex(strCreate).toUpperCase());
		resultMap.put("req_url", this.getRequestUrl());
		resultMap.put("orderSn", accountRechargeDTO.getTradeNo());
		log.info("resultMap:" + resultMap + ",strCreate:" + strCreate);
		return resultMap;
	}
	


	/**
	 * 使用 Map按key进行排序
	 * @param map
	 * @return
	 */
	public static Map<String, String> sortMapByKey(Map<String, String> map) {
		if (map == null || map.isEmpty()) {
			return null;
		}

		Map<String, String> sortMap = new TreeMap<String, String>(
				);

		sortMap.putAll(map);

		return sortMap;
	}
	

	@Override
	public void paySucess(PaymentSerial paymentSerial, String returnText, PayStatus payStatus,
			HttpServletRequest request) {
		paymentDao.handle(paymentSerial, returnText, payStatus);
	}

	@Override
	public Boolean queryOrderResult(PaymentSerial paymentSerial) {
		// 根据充值流水号查询支付状态
		if (null != paymentSerial && paymentSerial.getStatus() == PayStatus.INIT.getValue()) {
			PluginConfig pluginConfig = getPluginConfig();
			// 数据库中获取支付相关参数
			Map<String, Object> pluginConfigMap = getPluginConfigMap(pluginConfig);
			log.info("【订单查询:" + paymentSerial.getOrderSn() + "userId" + paymentSerial.getUserId() + "】开始封装参数...");
			
			SortedMap<String, String> parameterMap = new TreeMap<String, String>();
			String appid = pluginConfigMap.get("appid").toString();
			String mch_id = pluginConfigMap.get("mch_id").toString();
			String out_trade_no = paymentSerial.getOrderSn().toString();
			String nonce_str = RandomStringUtils.random(32, "abcdefghijklmnopqrstuvwxyz0123456789");
			parameterMap.put("appid", appid);
			parameterMap.put("mch_id", mch_id);
			parameterMap.put("out_trade_no", out_trade_no);
			parameterMap.put("nonce_str", nonce_str);
			String strCreate = createSign(parameterMap, false) + "&key=" + pluginConfigMap.get("key").toString();
			parameterMap.put("sign",  DigestUtils.md5Hex(strCreate).toUpperCase());
			try {
				String result = WeixinPayUtils.sendPost("https://api.mch.weixin.qq.com/pay/orderquery", parameterMap);

				if (StringUtils.isEmpty(result)) {
					return null;
				}

				Map<String, String> resultMap = new HashMap<String, String>();
				resultMap = XmlUtils.getMapFromXML(result);
				if (resultMap.get("return_code").equalsIgnoreCase("SUCCESS") && resultMap.get("result_code").equalsIgnoreCase("SUCCESS")) {
					//支付成功
					if(resultMap.get("trade_state").equalsIgnoreCase("SUCCESS")){
						paymentDao.handle(paymentSerial, resultMap.get("transaction_id"), PayStatus.SUCCESS);
					}else if(resultMap.get("trade_state").equalsIgnoreCase("NOTPAY") || resultMap.get("trade_state").equalsIgnoreCase("PAYERROR")){
						//支付失败
						paymentDao.handle(paymentSerial, resultMap.get("trade_state_desc"), PayStatus.FAILURE);
					}else{
						log.info("tradeNo："+paymentSerial.getOrderSn()+"  message："+result);
					}
				}else if(resultMap.get("return_code").equalsIgnoreCase("SUCCESS") && resultMap.get("result_code").equalsIgnoreCase("FAIL")
						&& resultMap.get("err_code").equalsIgnoreCase("ORDERNOTEXIST")){ 
					//订单不存在
					paymentDao.handle(paymentSerial, resultMap.get("err_code_des"), PayStatus.FAILURE);	
					
				} else {
					log.info("【订单查询:" + paymentSerial.getOrderSn() + "userId" + paymentSerial.getUserId() + "】...result :" + result);
				}	
			} catch (Exception e) {
				log.error(e.getMessage(), e);
				return false;
			}
		}
		return true;
	}
	
	private Map<String, Object> getPluginConfigMap(PluginConfig pluginConfig) {
		Map<String, Object> pluginConfigMap = new HashMap<String, Object>();
		List<PluginConfigAttribute> pluginConfigAttributes = pluginConfigAttributeMapper
				.findByPluginId(pluginConfig.getId());
		for (int i = 0; i < pluginConfigAttributes.size(); i++) {
			PluginConfigAttribute pluginConfigAttribute = pluginConfigAttributes.get(i);
			pluginConfigMap.put(pluginConfigAttribute.getName(), pluginConfigAttribute.getAttributes());
		}
		return pluginConfigMap;
	}

}
